import { c2n } from "./serialize";
import { Waiter } from "./utils";

export class Subject {
    private _obs: SubjectListener[] = [];
    clean() {
        this._obs = [];
    }
    once<T>(eventName: string | { new(): T }, callback: (msg: any, eventName: string) => void, target: any): SubjectListener {
        let ls = this.on(eventName, callback, target);
        ls.isOnce = true;
        return ls;
    }
    on<T>(eventName: string | { new(): T }, callback: (msg: T, eventName: string) => void, target: any): SubjectListener {
        if (typeof eventName !== "string") {
            eventName = c2n(eventName) || eventName.name;
        }
        let sl = new SubjectListener();
        sl.eventName = eventName;
        sl.callback = callback;
        sl.target = target;
        sl.isOnce = false;
        this._obs.push(sl);
        return sl;
    }
    off<T>(target: any, eventName?: string | { new(): T }, callback?: (msg: T, eventName: string) => void): void {
        if (eventName && typeof eventName !== "string") {
            eventName = c2n(eventName) || eventName.name;
        }

        let list = this._obs.slice();
        for (var i = 0; i < list.length; i++) {
            let listener = list[i];
            if (eventName && listener.eventName != eventName) {
                continue;
            }
            if (callback && listener.callback != callback) {
                continue;
            }

            if (listener.target === target) {
                this.delListener(listener);
            }
        }
    }
    delListener(ls: SubjectListener) {
        for (let i = 0; i < this._obs.length; i++) {
            if (this._obs[i] === ls) {
                this._obs.splice(i, 1);
                break;
            }
        }
    }

    emit<T>(eventName: string | { new(): T }, msg?: T) {
        if (typeof eventName !== "string") {
            eventName = c2n(eventName) || eventName.name;
        }
        let list = this._obs.slice();
        for (var i = 0; i < list.length; i++) {
            let listener = list[i];
            if (listener.eventName === eventName && listener.callback) {
                listener.callback.call(listener.target, msg, eventName);
                if (listener.isOnce) {
                    this.delListener(listener);
                }
            }
        }
    }
};

export class SubjectListener {
    eventName: string;
    callback: (msg: any, eventName: string) => void;
    target: any;
    isOnce: boolean;
};

const { ccclass, property } = cc._decorator;
@ccclass
export class SubjectComponent extends cc.Component {
    private _subject: Subject = new Subject();

    once<T>(eventName: string | { new(): T }, callback: (msg: any, eventName: string) => void, target: any): SubjectListener {
        return this._subject.once(eventName, callback, target);
    }

    on<T>(eventName: string | { new(): T }, callback: (msg: T, eventName: string) => void, target: any): SubjectListener {
        return this._subject.on(eventName, callback, target);
    }

    off<T>(target: any, eventName?: string | { new(): T }, callback?: (msg: T, eventName: string) => void): void {
        return this._subject.off(target, eventName, callback);
    }

    emit<T>(eventName: string | { new(): T }, msg?: T) {
        this._subject.emit(eventName, msg);
    }
};

export class MessageSync {
    private __waiter: Waiter<void> = new Waiter();
    [key: string]: any;
    constructor(msg: any) {
        Object.assign(this, msg);
    }
    finish() {
        this.__waiter.finish();
    }
    async await() {
        await this.__waiter.await();
    }
};

let MsgHub = new Subject();
export default MsgHub;